#include "Audio.h"

// *************************************** LECTEUR

Lecteur::Lecteur(int    _frequence          ,
		 Uint16 _format             ,
		 Uint8  _nbcanaux           ,
		 Uint16 _taille_audiobuffer ) {

  SDL_AudioSpec sortie;
  SDL_AudioSpec obtenu;

  mutex           = SDL_CreateMutex();
  sortie.freq     = _frequence;
  sortie.format   = _format;
  sortie.channels = _nbcanaux;
  sortie.samples  = _taille_audiobuffer;
  sortie.callback = callback_audio; // membre statique de la classe
  sortie.userdata = this; // l'instance de lecteur est la donn�e utilisateur

  if (SDL_OpenAudio(&sortie, &obtenu)) statut = Erreur;
  else {
    // Il n'y a pas eu d'erreur => on m�morise les param�tres obtenus :
    statut    = Stop;
    frequence = obtenu.freq;
    format    = obtenu.format;
    nbcanaux  = obtenu.channels;
    silence   = obtenu.silence;
    switch(format) {
    case AUDIO_S8 : taille_bloc = nbcanaux; break;
    case AUDIO_U8 : taille_bloc = nbcanaux; break;
    default       : taille_bloc = 2*nbcanaux;
    }
  }
}

Lecteur::~Lecteur() {
  if (statut!=Erreur) {
    SDL_LockMutex(mutex);   // des lectures sont peut-�tre en cours
    SDL_CloseAudio();       // fin du thread audio
    SDL_UnlockMutex(mutex); // On d�v�rouille
  }
}

void Lecteur::lecture() {
  if (statut!=Erreur) {
    SDL_PauseAudio(0);
    statut = Play;
  }
}

void Lecteur::pause() {
  if (statut!=Erreur) {
    SDL_PauseAudio(1);
    statut = Pause;
  }
}

void Lecteur::callback_audio(void *udata, Uint8 *stream, int len) {
  Lecteur * lecteur = (Lecteur *)udata;
  
  SDL_LockMutex(lecteur->mutex); // on bloque la lib�ration des donn�es
  if(lecteur->statut!=Erreur)
    for (vector<Source *>::iterator it = lecteur->sources.begin();
	 it != lecteur->sources.end();
	 it++)
      (*it)->mix_audio(stream, len);
  SDL_UnlockMutex(lecteur->mutex); // On d�v�rouille
}

// *************************************** SOURCE

Source::Source(Lecteur * _lecteur, char * _fichier)
  : fichier(_fichier)
{
  SDL_AudioSpec wav;
  Uint8 *       wav_buf;
  Uint32        wav_len;
  SDL_AudioCVT  wav_cvt;

  lecteur = _lecteur;
  buffer  = NULL;

  // On v�rifie d'abord que le lecteur ne pose pas de probl�me :
  erreur = (lecteur->statut == Lecteur::Erreur);
  // S'il ny a pas d'erreur, on charge le fichier :
  if (!erreur) {
    erreur = ( SDL_LoadWAV(fichier, &wav, &wav_buf, &wav_len) == NULL );
   // S'il ny a pas eu d'erreur, on prepare la conversion :
    if (!erreur) {
      erreur = ( SDL_BuildAudioCVT
		 (&wav_cvt,
		  wav.format,      wav.channels,      wav.freq,
		  lecteur->format, lecteur->nbcanaux, lecteur->frequence)
		 < 0 );
      // s'il n'y a pas eu d'erreur, on fait la conversion :
      if (!erreur) {
	buffer = (Uint8*)malloc(wav_len * wav_cvt.len_mult);
	erreur = (buffer == NULL);
	// s'il n'y a pas eu de probl�me d'allocation, on continue :
	if (!erreur) {
	  memcpy(buffer, wav_buf, wav_len);
	  wav_cvt.len = wav_len;
	  wav_cvt.buf = buffer;  
	  erreur = SDL_ConvertAudio(&wav_cvt);
	  // s'il n'y a pas eu d'erreur, on calcule les attributs deriv�s,
	  // et on inscrit la source dans le lecteur :
	  if (!erreur) {
	    taille = wav_cvt.len_cvt;
	    duree = (float)(taille/lecteur->taille_bloc)/lecteur->frequence;
	    SDL_LockMutex(lecteur->mutex);   // lectures peut-�tre en cours ?
	    lecteur->sources.push_back(this);
	    SDL_UnlockMutex(lecteur->mutex); // On d�v�rouille
	  }
	}
      }
      // on libere ce qui a ete alloue par SDL_LoadWAV :
      SDL_FreeWAV(wav_buf);
    }
  }
}

Source::~Source() {
  SDL_LockMutex(lecteur->mutex);   // des lectures sont peut-�tre en cours
  if (buffer!=NULL) free(buffer);
  if (!erreur) {
    // On d�sinscrit la source dans le lecteur :
    for (vector<Source *>::iterator it = lecteur->sources.begin();
	 it != lecteur->sources.end(); it++) {
      Source * source = *it;
      if (source == this) {
	lecteur->sources.erase(it);
	break;
      }
    }
    // On supprime les lectures :
    for (vector<Lecture *>::iterator it = lectures.begin();
	 it != lectures.end(); it = lectures.erase(it))
      delete(*it);
  }
  SDL_UnlockMutex(lecteur->mutex); // On d�v�rouille
}

void Source::mix_audio(Uint8 *stream, int len) {
  if(!erreur) {
    for (vector<Lecture *>::iterator it = lectures.begin();
	 it != lectures.end(); ) {
      Lecture * lecture = *it;
      if (lecture->statut == Lecture::Play) lecture->mix_audio(stream, len);
      // on enleve les lectures volatiles finies :
      if (lecture->type == Lecture::Volatile
	  && lecture->statut == Lecture::Stop) {
        it = lectures.erase(it);
        delete(lecture);
      }
      else it++;
    }
  }
}

// *************************************** LECTURE

void Lecture::creer_volatile(Source * _source) {
  new Lecture(Volatile, _source, Play);
}

Lecture * Lecture::creer_persistante(Source * _source,
				     Statut _statut)
{
  return new Lecture(Persistante, _source, _statut);
}

Lecture::Lecture(Type _type, Source * _source, Statut   _statut) {
  source   = _source;
  type     = _type;
  statut   = _statut;
  i_buffer = 0;
  // On inscrit la lecture dans la source :
  if (!source->erreur) {
    SDL_LockMutex(source->lecteur->mutex);
    source->lectures.push_back(this);
    SDL_UnlockMutex(source->lecteur->mutex);
  }
}

Lecture::~Lecture() {
  SDL_LockMutex(source->lecteur->mutex);
  SDL_UnlockMutex(source->lecteur->mutex);
}

void Lecture::lecture() {
  statut = Play;
}

void Lecture::pause() {
  statut = Pause;
}

void Lecture::stop() {
  statut = Stop;
  i_buffer = 0;
}

void Lecture::vaporise(Quand quand) {
  type = Volatile;
  if (quand == Maintenant) stop();
}

void Lecture::mix_audio(Uint8 *stream, int len) {
  Uint8 temp[len];
  int i;

  for (i=0; i<len && i_buffer<source->taille; i++, i_buffer++)
    temp[i] = source->buffer[i_buffer];
  
  SDL_MixAudio(stream, temp, i, SDL_MIX_MAXVOLUME);

  if (i_buffer == source->taille) stop();
}

// *************************************** LECTURERVVB

void LectureRVVB::creer_volatile(Source * _source,
				 float    _volume,
				 float    _vitesse,
				 float    _balance) {
  new LectureRVVB(Volatile, _source, Play,
		  _volume, _vitesse, _balance,
		  false);
}

LectureRVVB * LectureRVVB::creer_persistante(Source * _source,
					     float    _volume,
					     float    _vitesse,
					     float    _balance,
					     Statut   _statut,
					     bool     _repetition) {
  return new LectureRVVB(Persistante, _source, _statut,
			 _volume, _vitesse, _balance,
			 _repetition);
}

LectureRVVB::LectureRVVB(Type _type, Source * _source, Statut _statut,
			 float _volume, float _vitesse, float _balance,
			 bool _repetition) 
  : Lecture(_type, _source, _statut)
{
  if (!source->erreur) {
    // On rev�rouille le lecteur pour le reste de l'initialisation :
    SDL_LockMutex(source->lecteur->mutex);
    repetition   = _repetition;
    volume       = _volume;
    vitesse      = _vitesse;
    balance      = _balance;
    taille_bloc  = source->lecteur->taille_bloc;
    dernier_bloc = source->taille/taille_bloc - 1;
    position     = vitesse<0 ? dernier_bloc : 0;
    SDL_UnlockMutex(source->lecteur->mutex);
  }
}

LectureRVVB::~LectureRVVB() {
  SDL_LockMutex(source->lecteur->mutex);
  SDL_UnlockMutex(source->lecteur->mutex);
}

void LectureRVVB::stop() {
  statut = Stop;
  if (vitesse < 0) position = dernier_bloc;
  else             position = 0;
}

void LectureRVVB::vaporise(Quand quand) {
  Lecture::vaporise(quand);
  repetition = false;
}

void LectureRVVB::mix_audio(Uint8 *stream, int len) {
  Uint8 tempG[len]; // buffer contenant la piste de gauche
  Uint8 tempD[len]; // buffer contenant la piste de droite
  int i;            // indice de remplissage de tempG et tempD
  int j;            // indice d'ecriture d'un bloc d'echantillon

  i=0;
  while (i<len && statut==Play) {
    i_buffer = (int)position*taille_bloc;
    // remplissage des buffers
    for (j=0; j<taille_bloc/2; j++) {
      tempG[i+j] = source->buffer[i_buffer+j];
      tempD[i+j] = source->lecteur->silence;
    }
    for (j=taille_bloc/2; j<taille_bloc; j++) {
      tempD[i+j] = source->buffer[i_buffer+j];
      tempG[i+j] = source->lecteur->silence;
    }
    // mise a jour des indices
    i += taille_bloc;
    position += vitesse;
    if (position < 0 || position > dernier_bloc) {
      if (!repetition)  statut = Stop;
      if (position < 0) position = dernier_bloc;
      else              position = 0;
    }
  }
  
  // On mixe la lecture courante :
  SDL_MixAudio(stream, tempG, i,
	       (int)(volume*(1.0-balance)*SDL_MIX_MAXVOLUME));
  SDL_MixAudio(stream, tempD, i,
	       (int)(volume*balance      *SDL_MIX_MAXVOLUME));
}

// *************************************** OPERATEURS D'AFFICHAGE

ostream & operator << (ostream & out, const Lecteur & l) {
  char * ch_format;
    
  switch(l.format) {
  case AUDIO_S8     : ch_format = "AUDIO_S8"; break;
  case AUDIO_S16    : ch_format = "AUDIO_S16"; break;
  case AUDIO_U8     : ch_format = "AUDIO_U8"; break;
  case AUDIO_U16    : ch_format = "AUDIO_U16"; break;
  case AUDIO_S16MSB : ch_format = "AUDIO_S16MSB"; break;
  case AUDIO_U16MSB : ch_format = "AUDIO_U16MSB"; break;
  default           : ch_format = "?";
  }
  
  out << "====== LECTEUR ======\n";

  if (l.statut == Lecteur::Erreur)
    out << "*** ERREUR : " <<  SDL_GetError() << endl;
  else
    out << "Etat      : " << (SDL_GetAudioStatus()==SDL_AUDIO_PLAYING ?
			      "OUVERT" :
			      "FERME") << endl
	<< "Frequence : " << l.frequence << "Hz" << endl
	<< "Format    : " << ch_format << endl
	<< "Canaux    : " << (int)l.nbcanaux << endl
	<< "Sources   : " << l.sources.size() << endl;

  return out << flush;
}

ostream & operator << (ostream & out, const Source & s) {
  out << "SOURCE " << s.fichier;
  if (s.erreur)
    out << " *** ERREUR ";
  else {
    int minutesT =(int)(s.duree / 60);
    float secondesT = s.duree - (minutesT*60);
    char string[50];
    sprintf(string,
	    " (duree : %02i'%05.2f) lectures : %i",
	    minutesT, secondesT, s.lectures.size());
    out << string;
  }
  return out << flush;
}

ostream & operator << (ostream & out, const Lecture & l) {
  float instant_lect =
    (float)(l.i_buffer/l.source->lecteur->taille_bloc)
    /l.source->lecteur->frequence;
  int minutes = (int)(instant_lect / 60);
  float secondes = instant_lect - (minutes*60);
  int minutesT = (int)(l.source->duree / 60);
  float secondesT = l.source->duree - (minutesT*60);
  char string[150];
  sprintf(string,
	  "[ %s | %s | %02i'%05.2f / %02i'%05.2f ]",
	  (l.type == Lecture::Volatile ? "V" : "P"),
	  (l.statut == Lecture::Play ? "->" :
	   (l.statut == Lecture::Stop ? "__" : "||")),
	  minutes,
	  secondes,
	  minutesT,
	  secondesT
	  );
  return out << string << flush;
}

ostream & operator << (ostream & out, const LectureRVVB & l) {
  float instant_lect = l.position/l.source->lecteur->frequence;
  int minutes = (int)(instant_lect / 60);
  float secondes = instant_lect - (minutes*60);
  int minutesT = (int)(l.source->duree / 60);
  float secondesT = l.source->duree - (minutesT*60);
  char string[150];
  sprintf(string,
	  "[ %s | %s | %s | vit %.2f | bal %.2f | vol %.2f | %02i'%05.2f / %02i'%05.2f ]",
	  (l.type == Lecture::Volatile ? "V" : "P"),
	  (l.statut == Lecture::Play ? "->" :
	   (l.statut == Lecture::Stop ? "__" : "||")),
	  (l.repetition ? "R" : "1"),
	  l.vitesse,
	  l.balance,
	  l.volume,
	  minutes,
	  secondes,
	  minutesT,
	  secondesT
	  );
  return out << string << flush;
}
